#include "platform.h"
#include <pryn.h>

static LRESULT CALLBACK windowProcedure (HWND window, UINT message, WPARAM a, LPARAM b);

PrynGuiExport (void *, prynGuiControlWindow, (PrynGuiControl *control)) { return control ? control->pPlatform : 0; }
PrynGuiExport (PrynResult, prynGuiControlShow, (PrynGuiControl *control)) { if (!control || !control->pPlatform) return PrynResultNullArgument; ShowWindow (control->pPlatform, SW_SHOW); return PrynResultSuccess; }

static LPCSTR cursorToWindows (PrynGuiCursor cursor)
{
	switch (cursor)
	{
		case PrynGuiCursorNone: return IDC_ARROW;
		case PrynGuiCursorArrow: return IDC_ARROW;
		case PrynGuiCursorCrosshair: return IDC_CROSS;
		case PrynGuiCursorDrag: return IDC_HAND;
		case PrynGuiCursorHelp: return IDC_HELP;
		case PrynGuiCursorNo: return IDC_NO;
		case PrynGuiCursorProcessing: return IDC_APPSTARTING; 
		case PrynGuiCursorSizeAll: return IDC_SIZEALL;
		case PrynGuiCursorSizeLeftUpAndDownRight: return IDC_SIZENESW;
		case PrynGuiCursorSizeUpAndDown: return IDC_SIZENS;
		case PrynGuiCursorSizeRightUpAndDownLeft: return IDC_SIZENWSE;
		case PrynGuiCursorSizeLeftAndRight: return IDC_SIZEWE;
		case PrynGuiCursorText: return IDC_IBEAM;
		case PrynGuiCursorWait: return IDC_WAIT;
	}
	return 0;
}

static PrynResult destroyStateWindows (PrynTag *tag)
{
	PrynState *state;
	PrynGuiStateWindows *info;

	prynTagState (tag, &state);
	prynTagData (tag, (void **) &info);
	
	prynFree (state, info);
	return 0;
}

PrynGuiExport (PrynResult, prynGuiStateWindows, (PrynGuiStateWindows **output, PrynState *state))
{
	PrynStartFunctionHasState ();
	PrynGuiStateWindows *info = 0;
	PrynTag *tag;
	PrynTagList *list;
	HMODULE library;
	PrynString id;
	PrynObject object;

	if (output) *output = 0;
	prynAssert (state, NullArgument);

	prynStateTags (state, &list);
	prynStringLiteral (&id, PrynGuiStateWindowsTagId);
	prynTagFindDataById ((void **) &info, list, &id);

	if (info) goto success;
	
	prynCall (prynAllocateClearedObject (state, &info));
	prynStateToObject (state, &object);

	prynCall (prynTagCreate (&tag, &object, &id, info));
	info->instance = GetModuleHandleA (0);
	
	#define get(NAME) *(void **) &info->NAME = GetProcAddress (library, #NAME)
	
	library = info->libraryGdi32 = LoadLibraryA ("gdi32.dll");
	if (library)
	{
		get (ExtCreatePen);
		get (CreateDIBitmap);
		get (PlgBlt);
	}
	
	#undef get
	
	prynTagSetDestroyFunction (tag, &destroyStateWindows);
	PrynEndFunctionAfter (
		// success:
		{
			if (output) *output = info;
		},
		// failure:
		{
			prynFree (state, info);
		},
		// common:
		{
		});
}

PrynGuiExport (PrynResult, prynGuiControlCreateFromHwnd, (PrynGuiControl **output, PrynState *state, void *window))
{
	PrynStartFunctionHasState ();
	PrynGuiControl *control;

	if (output) *output = 0;
	prynAssert (output && state && window, NullArgument);
	
	prynGuiStateWindows (0, state);
	prynCall (prynAllocateClearedObject (state, &control));
	SetWindowLongA (window, GWL_WNDPROC, (LONG) &windowProcedure);
	SetPropA (window, "Pryn.Control", control);
	control->pState = state;
	control->pPlatform = window;
	control->pCursor = PrynGuiCursorArrow;

	if (output) *output = control;
	PrynEndFunction ();
}

PrynGuiExport (PrynResult, prynGuiControlGetClientOffsetInScreen, (PrynGuiControl *self, int *x, int *y))
{
	WINDOWINFO info;

	if (!self || !self->pPlatform)
		return PrynResultNullArgument;
	
	info.cbSize = sizeof (WINDOWINFO);
	if (!GetWindowInfo (self->pPlatform, &info))
		return PrynResultFailure;
	if (x) *x = info.rcClient.left;
	if (y) *y = info.rcClient.top;
	return PrynResultSuccess;
}

PrynGuiExport (PrynResult, prynGuiMessageBox, (PrynState *state, const PrynString *title, const PrynString *caption, PrynGuiMessageBoxType type))
{
	PrynResult result = PrynResultSuccess;
	const char *titlez = 0, *captionz = 0;
	UINT flags = 0;
	
	prynAssert (state && title && caption, NullArgument);
	
	prynStringToZ (title, state, &titlez);
	prynStringToZ (caption, state, &captionz);
	prynAssert (titlez && captionz, OutOfMemory);
	
	switch (type & PrynGuiMessageBoxTypeButtonMask)
	{
		case PrynGuiMessageBoxTypeOkay: flags |= MB_OK; break;
		default: break;
	}
	
	switch (type & PrynGuiMessageBoxTypeIconMask)
	{
		case PrynGuiMessageBoxTypeIconError: flags |= MB_ICONERROR;
		default: break;
	}
	
	prynAssert (!MessageBoxA (0, captionz, titlez, flags), Failure);
	
success:
	if (0)
	{
	failure:;
	}
	
	if (captionz != caption->pointer) prynFree (state, (void *) captionz);
	if (titlez != title->pointer) prynFree (state, (void *) titlez);
	
	return result;
}

PrynGuiExport (PrynResult, prynGuiControlDirty, (PrynGuiControl *self))
{
	if (!self || !self->pPlatform) return PrynResultNullArgument;
	if (!InvalidateRect (self->pPlatform, 0, 1)) return PrynResultFailure;
	return PrynResultSuccess;
}

PrynGuiExport (PrynResult, prynGuiControlDirtyArea, (PrynGuiControl *self, int left, int top, int right, int bottom))
{
	RECT rect;

	if (!self || !self->pPlatform) return PrynResultNullArgument;
	if (left > right) prynSwap (int, left, right);
	if (top > bottom) prynSwap (int, top, bottom);
	rect.left = left, rect.top = top, rect.right = right, rect.bottom = bottom;
	if (!InvalidateRect (self->pPlatform, &rect, 1)) return PrynResultFailure;
	return PrynResultSuccess;
}

PrynGuiExport (PrynResult, prynGuiControlSetCursor, (PrynGuiControl *control, PrynGuiCursor cursor))
{
	if (!control) return PrynResultNullArgument;
	control->pCursor = cursor;
	return PrynResultSuccess;
}

void WINAPI onTimer (HWND window, UINT message, UINT_PTR id, DWORD time)
{
	PrynGuiTimer *timer = (PrynGuiTimer *) id;
	
	if (!timer->delay) KillTimer (window, id);
	timer->function (timer->control, timer->data);
	
	prynDListRemove (timer->control->pTimers, timer);
	prynFree (timer->control->pState, timer);
}

PrynGuiExport (PrynResult, prynGuiControlTimer, (PrynGuiControl *control, int delayMilliseconds, void (*function) (PrynGuiControl *control, void *data), void *data))
{
	PrynGuiTimer *timer;

	if (!control || !function) return PrynResultNullArgument;
	
	for (timer = control->pTimers.first; timer; timer = timer->next)
		if (timer->function == function && timer->data == data)
			break;
	
	if (!timer)
	{
		prynAllocateClearedObject (control->pState, &timer);
		if (!timer) return PrynResultOutOfMemory;
		timer->control = control;
		prynDListAppend (control->pTimers, timer);
	}
	
	timer->delay = 0;
	timer->function = function;
	timer->data = data;
	SetTimer (control->pPlatform, (UINT_PTR) timer, delayMilliseconds, (TIMERPROC) &onTimer);
	return 0;
}

PrynGuiExport (PrynResult, prynGuiControlCreateWindow, (PrynGuiControl **output, PrynState *state, const PrynString *title, int clientWidth, int clientHeight))
{
	PrynStartFunctionHasState ();
	PrynGuiStateWindows *api = 0;
	DWORD style = WS_OVERLAPPED | WS_SYSMENU, exstyle = 0;
	RECT rect;
	const char *titlez;
	HWND window;

	if (output) *output = 0;
	prynAssert (state && output && title, NullArgument);
	prynCall (prynGuiStateWindows (&api, state));
	
	if (!api->windowClass)
	{
		WNDCLASS info;
		
		info.style = 0;
		info.lpfnWndProc = &DefWindowProc;
		info.cbClsExtra = 0;
		info.cbWndExtra = 0;
		info.hInstance = api->instance;
		info.hIcon = 0;
		info.hCursor = 0;
		info.hbrBackground = 0;
		info.lpszMenuName = 0;
		info.lpszClassName = "Pryn Window Class";
		api->windowClass = RegisterClassA (&info);
		if (!api->windowClass) return 0;
	}
	
	rect.left = 0, rect.top = 0, rect.right = clientWidth, rect.bottom = clientHeight;
	AdjustWindowRectEx (&rect, style, 0, exstyle);
	
	prynStringToZ (title, state, &titlez);
	window = CreateWindowExA (exstyle, (char *) api->windowClass, titlez, style, CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top, 0, 0, api->instance, 0);
	if (titlez != title->pointer) prynFree (state, (void *) titlez);
	if (!window) return 0;
	
	prynCall (prynGuiControlCreateFromHwnd (output, state, window));
	PrynEndFunction ();
}

static LRESULT CALLBACK windowProcedure (HWND window, UINT message, WPARAM a, LPARAM b)
{
	PrynGuiControl *control = (PrynGuiControl *) GetPropA (window, "Pryn.Control");
	PrynState *state = control ? control->pState : 0;
	PrynResult result = 0;
	PrynGuiEvent event;
	
	#define clear(TYPE) prynGuiEventClear (&event, PrynGuiEventType##TYPE, control);

	#define fillMouseData() \
		do { \
			event.pMouseX = GET_X_LPARAM (b); \
			event.pMouseY = GET_Y_LPARAM (b); \
			event.pMouseLeftButton = !!(a & MK_LBUTTON); \
			event.pMouseRightButton = !!(a & MK_RBUTTON); \
			event.pMouseMiddleButton = !!(a & MK_MBUTTON); \
			event.pKeyControl = !!(a & MK_CONTROL); \
			event.pKeyShift = !!(a & MK_SHIFT); \
		} while (0)
			
	#define dispatch() prynGuiEventDispatch (&event), prynGuiEventEmpty (&event);
	
	if (state) switch (message)
	{
		case WM_NCHITTEST:
		case WM_GETICON:
		case WM_MOVE:
		case WM_SIZE:
		case WM_NCMOUSEMOVE:
			break;
			
		case WM_MOUSEMOVE:
			clear (MouseMove);
			fillMouseData ();
			dispatch ();
			return 1;
			
		case WM_LBUTTONDOWN: clear (MouseLeftButtonDown); fillMouseData (); dispatch (); return 1;
		case WM_LBUTTONUP: clear (MouseLeftButtonUp); fillMouseData (); dispatch (); return 1;
		case WM_RBUTTONDOWN: clear (MouseRightButtonDown); fillMouseData (); dispatch (); return 1;
		case WM_RBUTTONUP: clear (MouseRightButtonUp); fillMouseData (); dispatch (); return 1;
		case WM_MBUTTONDOWN: clear (MouseMiddleButtonDown); fillMouseData (); dispatch (); return 1;
		case WM_MBUTTONUP: clear (MouseMiddleButtonUp); fillMouseData (); dispatch (); return 1;

		case WM_SETCURSOR:
		{
			HCURSOR cursor = LoadCursor (0, cursorToWindows (control->pCursor));
			SetCursor (cursor);
			return 0;
		}
			
		case WM_PAINT:
		{
			PAINTSTRUCT paint;
			HDC hdc = BeginPaint (window, &paint);
			PrynGuiPainter *painter;

			if (!hdc) break;
			
			prynGuiPainterCreateForHwnd (&painter, state, window, hdc, &paint.rcPaint);
			clear (Paint);
			
			prynGuiEventSetPainter (&event, painter);
			dispatch ();
			prynGuiPainterDestroy (painter);
			
			EndPaint (window, &paint);
			break;
		}
			
		case WM_DESTROY: clear (Destroy); dispatch (); break;

		default:
			//prynPrintFormat (state, "Unknown message %xh (params %xh and %xh) encountered.\n", message, a, b);
			break;
	}
	
	return DefWindowProcA (window, message, a, b);
}

PrynGuiExport (PrynResult, prynGuiMessageLoop, (PrynState *state))
{
	PrynGuiStateWindows *api = 0;
	MSG message;
	
	if (!state) return PrynResultNullArgument;
	prynGuiStateWindows (&api, state);
	if (!api) return PrynResultFailure;
	
	while (!api->quitLoop)
	{
		bool first = true; // Causes the first message received to block but subsequent to not. This way we sleep between messages as much as possible.
		BOOL result;
	
		while ((result = first ? GetMessageW (&message, 0, 0, 0) : PeekMessageW (&message, 0, 0, 0, PM_REMOVE)) != 0 && !api->quitLoop)
		{
			first = false;
			if (result == -1)
				return -1;
	
			TranslateMessage (&message);
			DispatchMessageW (&message);
		}
	}
	
	api->quitLoop = false;
	return PrynResultSuccess;
}

PrynGuiExport (PrynResult, prynGuiQuitMessageLoop, (PrynState *state))
{
	PrynGuiStateWindows *api = 0;
	
	if (!state) return PrynResultNullArgument;
	prynGuiStateWindows (&api, state);
	if (api) api->quitLoop = true;
	return PrynResultSuccess;
}

PrynGuiExport (PrynResult, prynGuiControlSetMenu, (PrynGuiControl *control, PrynGuiMenu *menu))
{
	PrynStartFunction ();

	prynAssert (control, NullArgument);

	PrynEndFunction ();
}